package han.Chensing.CCirnosLibrary.command;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Stack;

import han.Chensing.CCirnosLibrary.CCirnoLibrary;
import han.Chensing.CCirnosLibrary.composer.CCirnoLibraryComposer;
import han.Chensing.CCirnosLibrary.func.NodeBatchDownloadCallback;
import han.Chensing.CCirnosLibrary.func.NodeDownloadCallback;
import han.Chensing.CCirnosLibrary.map.ItemDecInfo;
import han.Chensing.CCirnosLibrary.map.ItemNode;
import han.Chensing.CCirnosLibrary.util.Base64Util;

import static han.Chensing.CCirnosLibrary.P.e;
import static han.Chensing.CCirnosLibrary.P.i;

@SuppressWarnings("unused")
public class CommandResolver {

    private static final int OK = 0;
    private static final int EXCEPTION = -2;
    private static final int INVALID_PARAMETER = -3;
    private static final int INVALID_STATE = -4;
    private static final int QUIT = -9;

    public boolean loaded=false;
    public Stack<ItemNode> itemNodeStack;
    public CCirnoLibrary library;
    public CCirnoLibraryComposer libraryComposer;
    private String tempKey=null;

    public HashMap<String,ItemNode> nodeHashMap;

    List<ItemNode> tempList;

    public CommandResolver(){
        itemNodeStack=new Stack<>();
        library=new CCirnoLibrary();
        libraryComposer=new CCirnoLibraryComposer();
        tempList=new ArrayList<>();
        nodeHashMap=new HashMap<>();
    }

    @CCirnoCommand
    public int help(String[] strings){
        i("-------------------------------------------------------------");
        i("                  CCirnos Library console                    ");
        i("-------------------------------------------------------------");
        i("fetch [address]              Fetch index from remote         ");
        i("e_fetch {key} [address]      Fetch index from remote with key");
        i("load {path}                  Load index from local           ");
        i("e_load {key} {path}          Load index from local with key  ");
        i("unload                       Unload index                    ");
        i("get {label} {dic} [key]      Get from remote/local library   ");
        i("quit                         Quit console                    ");
        i("-------------------------------------------------------------");
        i("book [parent path]           List books&shelves              ");
        i("cd {path]                    Enter shelve                    ");
        i("mov {name} {label}           Register a book&shelve to label ");
        i("del [label]                  Delete a label/all labels       ");
        i("toB64 {label}                Get Base64 string from node     ");
        i("fromB64 {b64} {label}        Transform Base64 string to node ");
        i("-------------------------------------------------------------");
        i("compose {from} {to}          Compose a library               ");
        i("-------------------------------------------------------------");
        return OK;
    }

    @CCirnoCommand
    public int fromB64(String[] strings){
        if (strings.length<3)
            return INVALID_PARAMETER;
        try {
            ItemNode itemNode = Base64Util.base64ToObject(strings[1], ItemNode.class);
            nodeHashMap.put(strings[2],itemNode);
            System.out.println(itemNode.toString());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return EXCEPTION;
        }
        return OK;
    }

    @CCirnoCommand
    public int toB64(String[] strings){
        if (strings.length<2)
            return INVALID_PARAMETER;
        ItemNode itemNode = nodeHashMap.get(strings[1]);
        if (itemNode==null)
            return INVALID_PARAMETER;
        try {
            String string = Base64Util.objectToBase64String(itemNode);
            System.out.println(string);
        } catch (IOException e) {
            e.printStackTrace();
            return EXCEPTION;
        }
        return OK;
    }

    @CCirnoCommand
    public int compose(String[] strings){
        if (strings.length<3)
            return INVALID_PARAMETER;
        libraryComposer.setSrcPath(new File(strings[1]));
        libraryComposer.setOutPath(new File(strings[2]));
        libraryComposer.compose();
        return OK;
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    @CCirnoCommand
    public int get(String[] strings){
        if (!loaded)
            return INVALID_STATE;
        if (strings.length<3)
            return INVALID_PARAMETER;
        ItemNode itemNode = nodeHashMap.get(strings[1]);
        if (itemNode==null)
            return INVALID_PARAMETER;
        File dic=new File(strings[2]);
        dic.mkdirs();
        ItemDecInfo itemDecInfo=null;
        if (strings.length>=4)
            itemDecInfo=new ItemDecInfo(){{
                setP(strings[3]);
            }};
        itemNode.setAllItemDecInfo(itemDecInfo);
        NodeBatchDownloadCallback batchDownloadCallback = new NodeBatchDownloadCallback() {
            @Override
            public void onProgressUpdate(ItemNode nowNode) {
                System.out.print("[CSL][I] D:" + nowNode.getName());
            }
            @Override
            public void onPartProgressUpdate(int blockNo, int blockAll) {
                System.out.print(".");
            }
            @Override
            public void onComplete(File dst) {
                System.out.println(" Done");
            }
        };
        if (itemNode.isList()) {
            library.downloadListNode(itemNode, dic, batchDownloadCallback);
        } else {
            try {
                System.out.print("[CSL][I] D:" + itemNode.getName());
                library.downloadNode(itemNode, new File(dic, itemNode.getName()),
                        new NodeDownloadCallback() {
                            @Override
                            public void onCopy(int len) {
                            }

                            @Override
                            public void onProgressUpdate(int blockNo, int blockAll) {
                                System.out.print(".");
                            }

                            @Override
                            public void onComplete(File dst) {
                                System.out.println(" Done");
                            }
                        });
            }catch (Exception e){
                e.printStackTrace();
                return EXCEPTION;
            }
        }
        return OK;
    }

    @CCirnoCommand
    public int del(String[] strings){
        if (strings.length==1) {
            cleanLabel();
            return OK;
        }
        ItemNode remove = nodeHashMap.remove(strings[1]);
        if (remove==null)
            return INVALID_STATE;
        return OK;
    }

    @CCirnoCommand
    public int mov(String[] strings){
        if (!loaded)
            return INVALID_STATE;
        if (strings.length<3)
            return INVALID_PARAMETER;
        ItemNode peek = itemNodeStack.peek();
        peek.find(node ->
                Objects.equals(node.getName(),strings[1])||
                Objects.equals(node.getLink(),strings[1]),
                tempList,false);
        if (tempList.isEmpty())
            return INVALID_STATE;
        ItemNode itemNode = tempList.get(tempList.size()-1);
        nodeHashMap.put(strings[2],itemNode);
        return OK;
    }

    @CCirnoCommand
    public int cd(String[] strings){
        if (!loaded)
            return INVALID_STATE;
        if (strings.length<2)
            return INVALID_PARAMETER;
        if (strings[1].equals("..")){
            if (itemNodeStack.size()<=1)
                return INVALID_STATE;
            itemNodeStack.pop();
            return OK;
        }
        tempList.clear();
        ItemNode peek = itemNodeStack.peek();
        peek.find(node -> Objects.equals(node.getName(),strings[1]),tempList,false);
        if (tempList.isEmpty())
            return INVALID_STATE;
        itemNodeStack.push(tempList.get(0));
        return OK;
    }

    @CCirnoCommand
    public int book(String[] strings){
        if (!loaded)
            return INVALID_STATE;
        ItemNode peek = itemNodeStack.peek();
        if (peek==null)
            return INVALID_STATE;
        peek.printMap(false);
        return OK;
    }

    @CCirnoCommand
    public int quit(String[] strings){
        i("Goodbye");
        return QUIT;
    }

    @CCirnoCommand
    public int unload(String[] strings){
        cleanLabel();
        library.unload();
        loaded=false;
        return OK;
    }

    @CCirnoCommand
    public int e_fetch(String[] strings){
        if (strings.length<2)
            return INVALID_PARAMETER;
        tempKey=strings[1];
        if (tempKey==null)
            return INVALID_PARAMETER;
        String[] stringsIn=strings.length<=2?new String[0]:new String[]{"fetch",strings[2]};
        return fetch(stringsIn);
    }

    @CCirnoCommand
    public int fetch(String[] strings){
        if (loaded){
            i("Unload first");
            return INVALID_STATE;
        }
        try{

            String address;
            if (strings.length<=1)
                address=CCirnoLibrary.DEFAULT_ADDRESS;
            else
                address=strings[1];
            library.unload();
            library.fetchMenuList(address,tempKey);
            return finallyLoad();
        }catch (FileNotFoundException fileNotFoundException){
            e("No index file found"+(tempKey==null?", is index file encrypted? (try e_fetch)":""));
            return EXCEPTION;
        } catch (Exception e){
            e.printStackTrace();
            return EXCEPTION;
        }finally {
            tempKey=null;
        }
    }

    @CCirnoCommand
    public int e_load(String[] strings){
        if (strings.length<3)
            return INVALID_PARAMETER;
        tempKey=strings[1];
        if (tempKey==null)
            return INVALID_PARAMETER;
        return load(new String[]{"load",strings[2]});
    }

    @CCirnoCommand
    public int load(String[] strings) {
        if (loaded) {
            i("Unload first");
            return INVALID_STATE;
        }
        if (strings.length < 2)
            return INVALID_PARAMETER;
        try {
            library.fetchMenuList(new File(strings[1]),tempKey);
            return finallyLoad();
        } catch (FileNotFoundException fileNotFoundException){
            e("No index file found"+(tempKey==null?", is index file encrypted? (try e_load)":""));
            return EXCEPTION;
        }catch (Exception e){
            e.printStackTrace();
            return EXCEPTION;
        }
    }

    public int finallyLoad(){
        if (!library.getItemMap().isHasMagic()){
            e("Invalid index file"+(tempKey==null?"":",is key wrong?"));
            library.unload();
            return INVALID_PARAMETER;
        }
        loaded=true;
        itemNodeStack.clear();
        itemNodeStack.push(library.getItemMap().getRootNode());
        return OK;
    }

    public String getNowPath(){
        if (!loaded)
            return "Unloaded";
        StringBuilder path= new StringBuilder();
        itemNodeStack.forEach(node -> path.append(node.getOriginalName()).append("/"));
        return path.toString();
    }

    public void cleanLabel(){
        nodeHashMap.clear();
    }
}
